package com.yunfeisoft.common.base;

import com.yunfeisoft.common.session.SessionModel;
import com.yunfeisoft.common.sql.builder.SelectBuilder;
import com.yunfeisoft.common.sql.builder.WhereBuilder;
import com.yunfeisoft.common.sql.mapper.DefaultRowMapper;
import com.yunfeisoft.common.sql.page.PageSql;
import com.yunfeisoft.common.sql.record.DomainModel;
import com.yunfeisoft.common.sql.record.DomainModelContext;
import com.yunfeisoft.common.sql.record.ExtendType;
import com.yunfeisoft.common.sql.record.TableColumn;
import com.yunfeisoft.common.sql.type.JdbcType;
import com.yunfeisoft.common.sql.type.TypeHandler;
import com.yunfeisoft.common.sql.type.TypeHandlerRegistry;
import com.yunfeisoft.common.utils.KeyUtils;
import com.yunfeisoft.common.utils.Page;
import com.yunfeisoft.common.utils.WebUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.request.RequestContextHolder;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class BaseDaoImpl<T extends BaseModel, PK extends Serializable> implements BaseDao<T, PK>, InitializingBean {

    protected static final Logger log = LoggerFactory.getLogger(BaseDaoImpl.class);

    @Autowired
    protected JdbcTemplate jdbcTemplate;
    @Autowired
    protected PageSql pageSql;
    @Autowired
    protected ApplicationContext applicationContext;

    protected Class<T> modelClass;
    protected DomainModel domainModel;

    private static final ThreadLocal<String> aliasThreadLocal = new ThreadLocal<>();

    private static final int DEL_VALUE = 1;
    private static final int UNDEL_VALUE = 2;

    public BaseDaoImpl() {
    }

    @SuppressWarnings("unchecked")
    protected Class<T> autoGetDomainClass() {
        if (modelClass == null) {
            Type type = this.getClass().getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                modelClass = (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
            } else {
                throw new RuntimeException("SubClass must give the ActualTypeArguments");
            }
        }
        return modelClass;
    }

    private String getDelName() {
        String alias = aliasThreadLocal.get();
        if (StringUtils.isNotBlank(alias)) {
            aliasThreadLocal.remove();
            return alias + "isDel";
        }
        return "isDel";
    }

    /**
     * 判断实体的字段是否重复
     *
     * @param id         实体主键
     * @param filedValue 字段值
     * @param fieldName  字段名称
     * @return
     */
    protected boolean isDuplicateField(PK id, Object filedValue, String fieldName) {
        WhereBuilder wb = new WhereBuilder();
        wb.andEquals(getDelName(), UNDEL_VALUE);
        wb.andEquals(fieldName, filedValue);
        wb.andNotEquals(domainModel.getPrimaryKey(), id);
        return count(wb) > 0;
    }

    protected void initModel(T t, boolean isAdd) {
        if (t == null) {
            return;
        }
        if (isAdd && StringUtils.isBlank(t.getId())) {
            t.setId(KeyUtils.getKey());
        }

        if (t.getModifyTime() == null) {
            t.setModifyTime(new Date());
        }

        if (isAdd && t.getCreateTime() == null) {
            t.setCreateTime(t.getModifyTime());
        }

        if (isAdd && t.getIsDel() == null) {
            t.setIsDel(UNDEL_VALUE);
        }

        SessionModel sessionModel = null;
        if (RequestContextHolder.getRequestAttributes() != null) {
            sessionModel = WebUtils.getLoginSessionModel();
        }

        if (sessionModel == null) {
            return;
        }

        if (StringUtils.isBlank(t.getModifyId())) {
            t.setModifyId(sessionModel.getUser().getId());
        }

        if (isAdd && StringUtils.isBlank(t.getCreateId())) {
            t.setCreateId(sessionModel.getUser().getId());
        }
    }

    /**
     * 获取添加实体的SQL语句
     *
     * @return
     */
    protected String getInsertSql() {
        String[] array = domainModel.joinColumnWithPlaceholder(", ");
        String sql = String.format("INSERT INTO %s (%s) VALUES (%s)", domainModel.getTableName(), array[0], array[1]);
        return sql;
    }

    /**
     * 将完整的SQL转换为统计SQL
     * 如：select a, b, c, t.d from table t
     * 转换后为：select count(1) from table t
     *
     * @param sql
     * @return
     */
    protected String toCountSql(String sql) {
        if (StringUtils.isEmpty(sql)) {
            return null;
        }
        //return sql.replaceFirst("(?<=(?i)SELECT).*?(?=(?i)FROM)", " COUNT\\(1\\) ").replaceAll("(?=(?i)order).*", "");
        return sql.toUpperCase().replaceFirst("(?<=(?i)SELECT).*?(?=(?i)FROM)", " COUNT\\(1\\) ").replaceAll("(ORDER\\s+BY)(.+)($).*", "");
    }

    protected String joinColumn(String separator) {
        return domainModel.joinColumn(separator);
    }

    /**
     * 添加实体
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int insert(T t) {
        Assert.notNull(t, "t is null");
        initModel(t, true);
        List<TableColumn> tableColumnList = domainModel.getTableColumnList();
        List<Object> list = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            ExtendType extendType = tableColumn.getExtendType();
            if (extendType != null && extendType.getCode() != ExtendType.DEFAULT.getCode()) {
                value = value.toString();
            }
            list.add(value);
        }
        return jdbcTemplate.update(getInsertSql(), list.toArray(new Object[0]));
    }

    /**
     * 批量添加实体
     *
     * @param list 实体对象列表
     * @return
     */
    @Override
    public int batchInsert(final List<T> list) {
        Assert.notNull(list, "list is null");
        final List<TableColumn> tableColumnList = domainModel.getTableColumnList();
        final TypeHandlerRegistry typeHandlerRegistry = DomainModelContext.getTypeHandlerRegistry();
        return jdbcTemplate.batchUpdate(getInsertSql(), new BatchPreparedStatementSetter() {
            @SuppressWarnings({"rawtypes", "unchecked"})
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                T t = list.get(i);
                initModel(t, true);
                int index = 1;
                for (int k = 0, size = tableColumnList.size(); k < size; k++) {
                    TableColumn tableColumn = tableColumnList.get(k);
                    if (tableColumn.isTransient()) {
                        continue;
                    }
                    Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
                    TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(tableColumn.getJavaType());
                    typeHandler.setParameter(ps, index, value, JdbcType.NULL);
                    index++;
                }
            }

            @Override
            public int getBatchSize() {
                return list.size();
            }
        }).length;
    }

    /**
     * 更新实体中的非空(不含null, "")字段
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int update(T t) {
        Assert.notNull(t, "t is null");
        initModel(t, false);
        WhereBuilder wb = new WhereBuilder();
        wb.andEquals(domainModel.getPrimaryKey(), t.getId());
        return updateByCondition(t, wb);
    }

    protected int updateByCondition(T t, WhereBuilder whereBuilder) {
        Assert.notNull(t, "t is null");
        initModel(t, false);
        StringBuilder sqlBuilder = new StringBuilder(String.format("UPDATE %s SET ", domainModel.getTableName()));
        List<TableColumn> tableColumnList = domainModel.getTableColumnList();

        List<Object> values = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            final TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            ExtendType extendType = tableColumn.getExtendType();
            if (extendType != null && extendType.getCode() != ExtendType.DEFAULT.getCode()) {
                value = value.toString();
            }
            if (value == null || (value instanceof String && StringUtils.isEmpty((String) value))) {
                continue;
            }

            values.add(value);
            sqlBuilder.append(tableColumn.getColumnName())
                    .append(" = ")
                    .append(tableColumn.getPlaceholder())
                    .append(", ");
        }

        if (values.size() > 0) {
            int length = sqlBuilder.length();
            sqlBuilder.delete(length - 2, length);
            String sql = sqlBuilder.toString();
            sql = whereBuilder.getSql(sql);
            values.addAll(whereBuilder.getParameterList());
            return jdbcTemplate.update(sql, values.toArray(new Object[0]));
        }
        return 0;
    }

    /**
     * 更新实体所有字段
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int updateForce(T t) {
        Assert.notNull(t, "t is null");
        t.setIsDel(UNDEL_VALUE);

        initModel(t, false);
        StringBuilder sqlBuilder = new StringBuilder(String.format("UPDATE %s SET ", domainModel.getTableName()));
        List<TableColumn> tableColumnList = domainModel.getTableColumnList();

        Method primaryKeyMethod = null;
        List<Object> values = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            final TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            if (tableColumn.isPrimaryKey()) {
                primaryKeyMethod = tableColumn.getFieldGetMethod();
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            values.add(value);
            sqlBuilder.append(tableColumn.getColumnName())
                    .append(" = ")
                    .append(tableColumn.getPlaceholder())
                    .append(", ");
        }

        int length = sqlBuilder.length();
        sqlBuilder.delete(length - 2, length);
        sqlBuilder.append(" WHERE ").append(domainModel.getPrimaryKey()).append(" = ?");
        values.add(ReflectionUtils.invokeMethod(primaryKeyMethod, t));
        return jdbcTemplate.update(sqlBuilder.toString(), values.toArray(new Object[0]));
    }

    /**
     * 根据主键删除实体
     *
     * @param id 实体主键值
     * @return
     */
    @Override
    public int delete(PK id) {
        Assert.notNull(id, "id is null");
        String sql = String.format("UPDATE %s SET IS_DEL_ = ? WHERE %s = ?", domainModel.getTableName(), domainModel.getPrimaryKey());
        return jdbcTemplate.update(sql, new Object[]{DEL_VALUE, id});
    }

    /**
     * 根据主键删除实体
     *
     * @param ids 实体主键值数组
     * @return
     */
    @Override
    public int delete(final PK[] ids) {
        Assert.notNull(ids, "ids is null");
        String sql = String.format("UPDATE %s SET IS_DEL_ = ? WHERE %s = ?", domainModel.getTableName(), domainModel.getPrimaryKey());
        int[] batchUpdate = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setObject(1, DEL_VALUE);
                ps.setObject(2, ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        });
        return batchUpdate.length;
    }

    /**
     * 查询实体列表
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected List<T> query(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s", domainModel.getDefaultColumnArrayStr(), domainModel.getTableName());
        return query(sql, wb);
    }

    /**
     * 查询实体列表
     *
     * @param sql select语句
     * @param wb  where语句拼接实例
     * @return
     */
    protected List<T> query(String sql, WhereBuilder wb) {
        wb = (wb == null) ? new WhereBuilder() : wb;
        wb.andEquals(getDelName(), UNDEL_VALUE);

        sql = wb.getSql(sql);
        Object args[] = wb.getParameters();

        return jdbcTemplate.query(sql, args, new DefaultRowMapper<T>(domainModel));
    }

    /**
     * 限制返回查询记录数量
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected List<T> queryLimit(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s", domainModel.getDefaultColumnArrayStr(), domainModel.getTableName());
        return queryLimit(sql, wb);
    }

    /**
     * 限制返回查询记录数量
     *
     * @param querySql select语句
     * @param wb       where语句拼接实例
     * @return
     */
    protected List<T> queryLimit(String querySql, WhereBuilder wb) {
        wb = (wb == null) ? new WhereBuilder() : wb;
        wb.andEquals(getDelName(), UNDEL_VALUE);

        int pageNum = 0, pageSize = 0;
        pageNum = wb.getPageNum();
        pageSize = wb.getPageSize();
        querySql = wb.getSql(querySql);
        Object[] args = wb.getParameters();
        String qsql = pageSql.getSql(querySql, pageNum, pageSize);

        return jdbcTemplate.query(qsql, args, new DefaultRowMapper<T>(domainModel));
    }

    /**
     * 按条件统计实体数量
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected int count(WhereBuilder wb) {
        String sql = String.format("SELECT COUNT(1) FROM %s", domainModel.getTableName());

        wb = (wb == null) ? new WhereBuilder() : wb;
        wb.andEquals(getDelName(), UNDEL_VALUE);
        sql = wb.getSql(sql);
        Object[] args = wb.getParameters();

        return jdbcTemplate.queryForObject(sql, args, Integer.class);
    }

    /**
     * 根据主键查询实体
     *
     * @param id 实体主键值
     * @return
     */
    @Override
    public T load(PK id) {
        String sql = String.format("SELECT %s FROM %s WHERE %s = ?", domainModel.getDefaultColumnArrayStr(), domainModel.getTableName(), domainModel.getPrimaryKey());
        List<T> list = jdbcTemplate.query(sql, new Object[]{id}, new DefaultRowMapper<T>(domainModel));
        return DataAccessUtils.singleResult(list);
    }

    /**
     * 按条件删除实体
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected int deleteByCondition(WhereBuilder wb) {
        String sql = String.format("UPDATE %s SET IS_DEL_ = %d", domainModel.getTableName(), DEL_VALUE);
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.update(sql, args);
    }

    /**
     * 分页查询
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s ", domainModel.getDefaultColumnArrayStr(), domainModel.getTableName());
        return queryPage(sql, wb);
    }

    /**
     * 分页查询
     *
     * @param sql select语句
     * @param wb  where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(String sql, WhereBuilder wb) {
        String countSql = toCountSql(sql);
        return queryPage(sql, countSql, wb);
    }

    /**
     * 分页查询
     *
     * @param querySql select语句
     * @param countSql count语句
     * @param wb       where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(String querySql, String countSql, WhereBuilder wb) {
        int pageNum = 0, pageSize = 0;

        wb = (wb == null) ? new WhereBuilder() : wb;
        wb.andEquals(getDelName(), UNDEL_VALUE);
        querySql = wb.getSql(querySql);
        Object[] args = wb.getParameters();
        pageSize = wb.getPageSize();
        pageNum = wb.getPageNum();

        String qsql = pageSql.getSql(querySql, pageNum, pageSize);
        List<T> list = jdbcTemplate.query(qsql, args, new DefaultRowMapper<T>(domainModel));

        Page<T> page = new Page<T>();
        page.setData(list);

        if (StringUtils.isNotEmpty(countSql)) {
            String csql = wb.getCountSql(countSql);
            long count = jdbcTemplate.queryForObject(csql, args, Long.class);
            page.setTotal(count);
        }
        return page;
    }

    /**
     * 初始化分页参数，分页时用到
     *
     * @param wb       where语句拼接实例
     * @param paramMap
     */
    protected void initPageParam(WhereBuilder wb, Map<String, Object> paramMap) {
        int pageSize = paramMap.get("pageSize") == null ? 0 : (int) paramMap.get("pageSize");
        int pageNum = paramMap.get("pageNum") == null ? 0 : (int) paramMap.get("pageNum");
        wb.setPageSize(pageSize);
        wb.setPageNum(pageNum);
    }

    /**
     * 创建select语句，把当前表的所有字段加入column中
     *
     * @param alias 当前表的别名，如：T、TA
     * @return
     */
    protected SelectBuilder getSelectBuilder(String alias) {
        aliasThreadLocal.set(alias + ".");
        String split = String.format(", %s.", alias);
        String column = String.format("%s.%s", alias, joinColumn(split));
        SelectBuilder selectBuilder = new SelectBuilder();
        selectBuilder.setFromClass(domainModel.getClazz());
        selectBuilder.from(domainModel.getTableName()).alias(alias).build();
        return selectBuilder.column(column);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        DomainModelContext domainModelContext = applicationContext.getBean(DomainModelContext.class);
        domainModel = domainModelContext.registerBean(autoGetDomainClass());
    }
}
